execa:像写 JS 一样写 Shell 命令
execa 是一个 Node.js 进程执行库,它让我们可以用更直观的方式在 JavaScript 中执行 Shell 命令。相比 Node.js 内置的 child_process,execa 提供了更友好的 API、Promise 支持以及更好的错误处理。
为什么选择 execa
| 特性 | child_process | execa |
|---|---|---|
| API 风格 | 回调 / 低级 Promise | 现代异步 API |
| 模板字符串 | 不支持 | 支持 $`command` 语法 |
| 错误处理 | 需要手动判断退出码 | 自动抛出带详细信息的错误 |
| 输出获取 | 需要手动拼接 stdout/stderr | 直接返回结构化结果 |
安装
pnpm add -D execa
bash
基本用法
模板字符串语法(推荐)
execa 提供了 $ 模板标签,使命令的书写方式接近 Shell 脚本:
import { $ } from 'execa'
// 基本执行
const { stdout } = await $`npm run build`
console.log(stdout)
// 支持动态参数
const task = 'build'
const { stdout: output } = await $`npm run ${task}`
// 支持超时配置
const { stdout: timedOutput } = await $({ timeout: 5000 })`npm run build`
// 全局配置复用
const timedExeca = $({ timeout: 5000 })
await timedExeca`npm run build`
await timedExeca`npm run test`
javascript
数组参数语法
适用于需要精确控制参数传递的场景:
import { execa } from 'execa'
// 数组语法:第一个参数是命令,第二个是参数数组
const { stdout } = await execa('npm', ['run', 'build'])
console.log(stdout)
// 指定工作目录
const { stdout: prismaOutput } = await execa(
'npx',
['prisma', 'generate'],
{ cwd: path.join(__dirname, '..') }
)
console.log(prismaOutput)
javascript
在构建脚本中替换 child_process
对比改造前后的代码:
改造前(child_process):
import { execSync } from 'child_process'
// 同步执行
const result = execSync('npx prisma generate', {
cwd: path.join(__dirname, '..'),
})
console.log(result.toString())
javascript
改造后(execa):
import { execa } from 'execa'
// 异步执行,代码更加清晰
const { stdout } = await execa('npx', ['prisma', 'generate'], {
cwd: path.join(__dirname, '..'),
})
console.log(stdout)
javascript
替换 npm run build 的执行:
// 改造前
const result = execSync('npm run build', {
cwd: path.join(__dirname, '..'),
})
console.log(result.toString())
// 改造后
const { stdout } = await $`npm run build`
console.log(stdout)
javascript
模板字符串传参注意事项
使用 $ 模板标签时,必须使用反引号(backtick)而非普通引号:
// 错误写法 -- 会报错 "command failed with npm run build"
const { stdout } = await $('npm run build')
// 正确写法 -- 使用反引号
const { stdout } = await $`npm run build`
javascript
execa 在构建脚本中的完整集成
将上一节的 build.mjs 中的 child_process 调用全部替换为 execa:
// scripts/build.mjs
import { $, execa } from 'execa'
import path from 'path'
import { fileURLToPath } from 'url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
async function generatePrismaClients() {
for (const type of typesArr) {
// 替换 schema.prisma 中的 provider
// ...
// 使用 execa 数组语法执行 prisma generate
const { stdout } = await execa('npx', ['prisma', 'generate'], {
cwd: path.join(__dirname, '..'),
})
console.log(`type: ${type}`, stdout)
}
}
async function swcBuild() {
// 读取并修改 .swcrc 配置
// ...
// 使用 $ 模板语法执行构建
const { stdout } = await $`npm run build`
console.log(stdout)
// 恢复 .swcrc 配置
// ...
}
async function run() {
await generatePrismaClients()
await swcBuild()
}
run().catch(console.error)
javascript
execa 常用配置选项
await execa('command', ['args'], {
cwd: '/working/directory', // 工作目录
timeout: 30000, // 超时时间(毫秒)
env: { NODE_ENV: 'production' }, // 环境变量
input: 'stdin content', // 标准输入
reject: true, // 非零退出码时是否抛出错误(默认 true)
})
javascript
捕获命令输出
execa 返回一个包含完整执行结果的对象:
const result = await $`npm run build`
console.log(result.stdout) // 标准输出
console.log(result.stderr) // 标准错误
console.log(result.exitCode) // 退出码
console.log(result.command) // 实际执行的命令
javascript
小结
execa 是 child_process 的现代化替代方案,核心优势:
- 模板字符串语法:
$command`` 让代码可读性大幅提升 - Promise 原生支持:天然的 async/await 兼容
- 数组参数模式:精确控制参数,避免 Shell 注入风险
- 结构化返回值:直接解构获取 stdout、stderr、exitCode
在 NestJS 构建脚本中,execa 让命令执行逻辑变得更加简洁和可维护,是替代 child_process 的推荐选择。
↑